package org.mashart.pink.m.store;

/**
 * Used for buffered GPS data input / HTTP POST output
 * TODO: Is not used for the moment
 */
public class DataManager {

    /**
     * Unica istanza della classe <code>DataManager</code>. In una qualunque
     * esecuzione, il sistema non deve avere più di un buffer. La thread di
     * ricezione comunica alla thread di trasmissione i dati da trasmettere
     * immettendogli nel buffer, e quindi quest'ultimo deve essere unico.
     * Se la variabile <code>unique</code> è <code>null</code> il
     * <code>DataManager</code> non è ancora stato istanziato, altrimenti
     * <code>unique</code> è l'unico <code>DataManager</code> in memoria.
     */
    private static DataManager unique;

    /**
     * Vettore di stringhe contenente i dati inseriti nel buffer (e non ancora
     * rimossi). L'implementazione del buffer è una lista circolare (se si
     * arriva alla fine dell'array continuano ad inserirsi elementi nelle prime
     * posizioni), e si espande nel caso in cui le posizioni libere dell'array
     * siano finite.
     */
    private String[] data;

    /**
     * Quantità di elementi di cui cresce l'array nel caso in cui le posizioni
     * libere fossero finite e venisse effettuato un inserimento. Il valore di
     * questa variabile deve essere sempre maggiore o uguale ad <code>1</code>.
     */
    private int growthValue;

    /**
     * Intero che indica la posizione dell'array in cui inserire il prossimo
     * elemento.
     */
    private int head;

    /**
     * Intero che indica la posizione dell'array dalla quale prendere un
     * elemento nella prossima rimossione.
     */
    private int tail;

    /**
     * Intero che indica la quantità di elementi nel buffer.
     */
    private int size;

    /*
     * Intero che indica la dimensione iniziale dell'array che contiene gli
     * elementi del buffer.
     */
    //private int initialSize;
    /**
     * Unico costruttore. Costruisce un'istanza della classe
     * <code>DataManager</code> con un array di dimensione
     * <code>initialSize</code>, che cresce di <code>growthValue</code>
     * elementi ad ogni inserimento in cui il buffer è pieno.
     * Privato. Altre classi costruggono oggetti di tipo <code>DataManager</code>
     * utilizzando il metodo statico <code>getInstance(int, int)</code> della
     * stessa classe <code>DataManager</code>.
     * @param initialSize La dimensione iniziale del buffer.
     * @param growthValue La quantità di posizioni in più nell'array di dati
     * dopo una crescita del buffer.
     */
    private DataManager(int initialSize, int growthValue) {
        data = new String[initialSize];
        //this.initialSize = initialSize;
        this.growthValue = growthValue;
        head = tail = size = 0;
    }

    /**
     * Metodo factory che implementa il design pattern Singleton per la
     * produzione di un unica istanza della classe <code>DataManager</code>.
     * Se richiamato più volte, i parametri passati vengono tenuti in
     * considerazione solo la prima volta, per la costruzione dell'oggetto.
     * Le chiamate successive restituiscono l'oggetto senza modificarlo, ed i
     * parametri passati vengono ignorati.
     * @param initialSize La dimensione iniziale del buffer. Deve essere
     * maggiore di zero.
     * @param growRate La quantità di posizioni in più nell'array di dati
     * dopo una crescita del buffer. Anche questa deve essere maggiore di zero.
     * @return L'unico oggetto <code>DataManager</code> in memoria.
     */
    public static synchronized DataManager getInstance(int initialSize,
            int growRate) {
        if (unique == null) {
            unique = new DataManager(initialSize, growRate);
        }
        return unique;
    }

    /**
     * Inserisce un elemento nel buffer. Se nel buffer non ci sono posizioni
     * libere, la sua dimensione viene incrementata in modo da consentire altri
     * inserimenti.
     * @param frase La stringa da inserire nel buffer.
     */
    public synchronized void insert(String frase) {
        if (size == data.length) {
            grow();
        }
        data[head++] = frase;
        head %= data.length;
        size++;
        // Notifica le altre thread sull'inserimento dell'elemento.
        notify();
    }

    /**
     * Rimuove un elemento nel buffer e lo restituisce.
     * La chiamata a questo metodo è bloccante, e nel caso in cui il buffer non
     * contenesse nessun elemento, la thread chiamante in esecuzione aspetterà
     * fino a quando non venga realizzato un inserimento nel buffer da parte di
     * un altra thread.
     * @return La stringa rimossa dal buffer.
     */
    public synchronized String remove() {
        if (size == 0) {
            try {
                // Bloccati fino a quando un altra thread non inserisce un elemento.
                // oppure qualche thread segnala alla thread di finire l'attessa.
                wait();
            } catch (InterruptedException ex) {
                // In J2ME le thread non possono essere interrotte!
            }
        }
        if (size == 0) {
            return null;
        }
        String rS = data[tail];
        data[tail++] = null;
        size--;
        tail %= data.length;
        return rS;
    }

    /**
     * Sblocca un'eventuale thread in attessa per la rimossione di un elemento
     * nel caso di buffer vuoto.
     */
    public synchronized void unBlock() {
        notify();
    }

    /**
     * Restituisce la quantità di elementi nel buffer.
     * @return Un intero che indica la quantità di elementi nel buffer.
     */
    public synchronized int getSize() {
        return size;
    }

    /**
     * Procedura utilizzata per estendere l'array del buffer senza la perdita
     * delle informazioni in esso contenute.
     */
    private void grow() {
        String[] newCont = new String[data.length + growthValue];
        System.arraycopy(data, tail, newCont, 0, data.length - tail);
        System.arraycopy(data, 0, newCont, data.length - tail, head);
        size = head = data.length;
        tail = 0;
        data = newCont;
    }
}
